import { Injectable, BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { PersonaService } from '../persona/persona.service';
import { formatBeijingISO } from '../common/date.util';

@Injectable()
export class SessionService {
  constructor(
    private prisma: PrismaService,
    private personaService: PersonaService,
  ) {}

  // Create session
  async create(userId: string, personaId: string, skill: string) {
    if (!personaId) throw new BadRequestException('personaId is required');
    if (!skill) throw new BadRequestException('skill is required');
    const persona = await this.personaService.getById(personaId);
    if (!persona.isPublic && persona.creatorId !== userId) {
      throw new ForbiddenException('No permission to use this Persona');
    }
    if (persona.skills && persona.skills.length && !persona.skills.includes(skill)) {
      throw new BadRequestException('skill is not in the available list for this Persona');
    }

    const session = await this.prisma.session.create({
      data: ({
        userId,
        personaId,
        skill,
        personaSnapshot: {
          id: persona.id,
          name: persona.name,
          displayName: persona.displayName,
          description: persona.description,
          voiceId: persona.voiceId,
          language: persona.language,
          skills: persona.skills,
          avatar: persona.avatar,
          systemPrompt: persona.systemPrompt,
        },
        title: `${persona.displayName}`,
      } as any),
      select: { id: true, createdAt: true },
    });

    return {
      sessionId: session.id,
      persona: {
        id: persona.id,
        name: persona.name,
        displayName: persona.displayName,
        voiceId: persona.voiceId,
        language: persona.language,
        skills: persona.skills,
        avatar: persona.avatar,
      },
      createdAt: formatBeijingISO(session.createdAt as unknown as Date),
    };
  }

  // Get user session details
  async getByIdForUser(userId: string, sessionId: string) {
    const s = await this.prisma.session.findFirst({
      where: { id: sessionId, userId },
      select: {
        id: true,
        personaId: true,
        title: true,
        skill: true,
        personaSnapshot: true,
        createdAt: true,
        updatedAt: true,
      },
    });
    if (!s) return null;

    let fixed = false;
    let nextSkill = s.skill as string | null;
    let nextSnapshot = s.personaSnapshot as any | null;

    if (!nextSkill || !nextSnapshot) {
      const persona = await this.prisma.persona.findUnique({
        where: { id: s.personaId },
        select: {
          id: true,
          name: true,
          displayName: true,
          voiceId: true,
          language: true,
          skills: true,
          avatar: true,
        },
      });
      if (persona) {
        if (!nextSkill) nextSkill = persona.skills?.[0] ?? null;
        if (!nextSnapshot) {
          nextSnapshot = {
            id: persona.id,
            name: persona.name,
            displayName: persona.displayName,
            voiceId: persona.voiceId,
            language: persona.language,
            skills: persona.skills,
            avatar: persona.avatar,
          };
        }
        fixed = true;
      }
    }

    if (fixed) {
      await this.prisma.session.update({
        where: { id: s.id },
        data: { skill: nextSkill, personaSnapshot: nextSnapshot },
      });
      s.skill = nextSkill as any;
      s.personaSnapshot = nextSnapshot as any;
    }

    return {
      ...s,
      createdAt: formatBeijingISO(s.createdAt as unknown as Date),
      updatedAt: formatBeijingISO(s.updatedAt as unknown as Date),
    };
  }

  // Get user session list
  async listForUser(userId: string, params: { page?: number; pageSize?: number; order?: 'asc' | 'desc' }) {
    const page = Math.max(1, Number(params.page) || 1);
    const pageSize = Math.min(50, Math.max(1, Number(params.pageSize) || 10));
    const order = params.order === 'asc' ? 'asc' : 'desc';

    const [itemsRaw, total] = await this.prisma.$transaction([
      this.prisma.session.findMany({
        where: { userId },
        orderBy: { createdAt: order },
        skip: (page - 1) * pageSize,
        take: pageSize,
        select: {
          id: true,
          personaId: true,
          title: true,
          skill: true,
          personaSnapshot: true,
          isActive: true,
          createdAt: true,
          updatedAt: true,
        },
      }),
      this.prisma.session.count({ where: { userId } }),
    ]);

    // Read fix: batch fill missing fields
    const toFix = itemsRaw.filter((s) => !s.skill || !s.personaSnapshot);
    if (toFix.length > 0) {
      const personaIds = Array.from(new Set(toFix.map(s => s.personaId)));
      const personas = await this.prisma.persona.findMany({
        where: { id: { in: personaIds } },
        select: { id: true, name: true, displayName: true, voiceId: true, language: true, skills: true, avatar: true },
      });
      const map = new Map(personas.map(p => [p.id, p]));
      for (const s of toFix) {
        const p = map.get(s.personaId);
        if (!p) continue;
        const nextSkill = s.skill ?? (p.skills?.[0] ?? null);
        const nextSnapshot = s.personaSnapshot ?? {
          id: p.id, name: p.name, displayName: p.displayName, voiceId: p.voiceId, language: p.language, skills: p.skills, avatar: p.avatar,
        };
        await this.prisma.session.update({ where: { id: s.id }, data: { skill: nextSkill, personaSnapshot: nextSnapshot } });
        s.skill = nextSkill as any;
        s.personaSnapshot = nextSnapshot as any;
      }
    }

    const items = itemsRaw.map((s) => ({
      ...s,
      createdAt: formatBeijingISO(s.createdAt as unknown as Date),
      updatedAt: formatBeijingISO(s.updatedAt as unknown as Date),
    }));

    return { items, total, page, pageSize };
  }

  // Delete user session
  async deleteForUser(userId: string, sessionId: string) {
    // Verify session exists and belongs to current user
    const session = await this.prisma.session.findFirst({
      where: { id: sessionId, userId },
      select: { id: true },
    });

    if (!session) {
      throw new NotFoundException(`Session with ID ${sessionId} not found or access denied`);
    }

    // Use transaction to delete session and all related data
    await this.prisma.$transaction(async (tx) => {
      // Delete all messages for the session
      await tx.message.deleteMany({
        where: { sessionId },
      });

      // Delete session
      await tx.session.delete({
        where: { id: sessionId },
      });
    });

    return { success: true, message: 'Session and all messages have been deleted' };
  }
}